package edu.pku.sei.scriptengine.visitor;

import java.util.Collection;

import edu.pku.sei.mdabase.basictypes.CollectionType;
import edu.pku.sei.mdabase.basictypes.OCLInvalid;
import edu.pku.sei.mdabase.basictypes.OCLVoidValue;
import edu.pku.sei.mdabase.infrastructure.model.MAttribute;
import edu.pku.sei.mdabase.infrastructure.model.MClass;
import edu.pku.sei.mdabase.infrastructure.runtime.Context;
import edu.pku.sei.mdabase.infrastructure.runtime.Variable;
import edu.pku.sei.queryscript.parser.Ast.AtomicLiteralExpr;
import edu.pku.sei.queryscript.parser.Ast.AtomicVariableExpr;
import edu.pku.sei.queryscript.parser.Ast.AttributeCallExpr;
import edu.pku.sei.queryscript.parser.Ast.EqualExpr;
import edu.pku.sei.queryscript.parser.Ast.PlusExpr;

public class ExpressionEnforceVisitor extends ModelEnforceVisitor {
	
	public ExpressionEnforceVisitor(Context context) {
		super(context);
	}
	
	public ExpressionEnforceVisitor() {
		super();
	}

	@Override
	public Object visit(EqualExpr n) {
		QueryVisitor visitor = new ExpressionCheckVisitor();
		visitor.copyEnvironment(this);
		if (n.accept(visitor) == Boolean.TRUE) {
			return Boolean.TRUE;
		}
		
		//get value of left expression
		Object left = n.getLeftArithExpr().accept(this);
		
		//get value of right expression
		Object right = n.getRightArithExpr().accept(this);
		
		//if right is unbinding, binding right side
		if (right == OCLVoidValue.VOID) {
			right = n.getRightArithExpr().accept(this, left);	
		}
		//else use right side value binding left side
		else  {
			left = n.getLeftArithExpr().accept(this, right);
		}
		
		return n.accept(visitor);
	}


	@Override
	public Object visit(AtomicVariableExpr n, Object o) {
		String  variableName = (String) n.getVariableExpr().accept(this);
		Object obj = getVariableByName(variableName);
		
		if (obj instanceof Variable) {
			Variable vairable = (Variable) obj;
			Object value = history.getValue(vairable);
			Object type = vairable.getType();
			if (type instanceof CollectionType) {
				if (value instanceof Collection) {
					((Collection)value).add(o);
					history.commitAll();
					return value;
				} else {
					history.restoreUncommit();
					return OCLInvalid.INVALID;
				}
			} else {
				if (value == OCLVoidValue.VOID) {
					if (history.setValue(vairable, o)) {
						history.commitAll();
						return o;
					}
					else {
						history.restoreUncommit();
						return value;
					}
				} else {
					return OCLInvalid.INVALID;
				}
			}
		}
		else
			return OCLInvalid.INVALID;
	}
	
	@Override
	public Object visit(AtomicLiteralExpr n, Object o) {
		return super.visit(n);
	}
	
	@Override
	public Object visit(AttributeCallExpr n, Object o) {
		QueryVisitor checkOnlyVisitor = new ExpressionCheckVisitor();
		checkOnlyVisitor.copyEnvironment(this);
		Object checkResult = n.accept(checkOnlyVisitor);
		
		if (checkResult.equals(o)) {
			return checkResult;
		} else {
			Object variable =  n.getAtomicExpr().accept(this);
			String name = (String) n.getSimpleName().accept(this);
			if (variable == null) {
				return OCLInvalid.INVALID;
			} 
			//a.b
			else if (variable instanceof Variable) {
				Variable v = (Variable) variable;
				if (v.getValue() instanceof MClass) {
					MClass modelValue = (MClass) v.getValue();
					MAttribute attr = (MAttribute) modelValue.get(name);
					if (history.setValue(attr, o)) {
						history.commitAll();
						return o;
					} else {
						history.restoreUncommit();
						return history.getValue(attr);						
					}
				} else {
					return OCLInvalid.INVALID;
				}
				
			} 
			//a.b.c
			else if (variable instanceof MClass){
				MClass modelValue = (MClass) variable;
				MAttribute attr = (MAttribute) modelValue.get(name);
				if (history.setValue(attr, o)) {
					history.commitAll();
					return o;
				} else {
					history.restoreUncommit();
					return history.getValue(attr);
				}
			}
			else {
				return OCLInvalid.INVALID;
			}
		}
			
	}

}
